﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using Djs.Common.UI;

namespace SudokuGame.Sudoku
{
    public class SudokuUI : DblPanel
    {
        #region Konstrukce
        public SudokuUI()
        {
            this._Cells = new SudokuCellUI[9, 9];
            for (int r = 0; r < 9; r++)
                for (int c = 0; c < 9; c++)
                    this._Cells[r, c] = new SudokuCellUI(r, c);
            this._CellsCalc();
            this._InitialiseColors();
            this._Sheet = _PrepareDesignSheet();
        }
        private void _InitialiseColors()
        {
            this._EmptyCellColor = Color.Wheat;
            this._FixedCellColor = Color.LightBlue;
            this._SolvedCellColor = Color.LightGreen;
            this._ErrorCellColor = Color.LightCoral;
            this._ChangedCellBorderColor = Color.BlueViolet;
            this._CellBorderLightColor = Color.LightGray;
            this._CellBorderDarkColor = Color.Gray;
            this._CellBorderInnerColor = Color.DarkGray;
        }
        private SudokuSheet _PrepareDesignSheet()
        {
            SudokuSheet sheet = SudokuSheet.CreateSheet(7);
            if (!sheet[5, 2].HasValue) sheet[5, 2] = 5;
            else if (!sheet[3, 6].HasValue) sheet[3, 6] = 5;
            else if (!sheet[4, 3].HasValue) sheet[4, 3] = 5;
            else if (!sheet[6, 1].HasValue) sheet[6, 1] = 5;
            else if (!sheet[0, 7].HasValue) sheet[0, 7] = 5;

            if (!sheet[4, 6].HasValue) sheet[4, 6] = 5;
            else if (!sheet[2, 2].HasValue) sheet[2, 2] = 5;
            else if (!sheet[3, 5].HasValue) sheet[3, 5] = 5;
            else if (!sheet[5, 7].HasValue) sheet[5, 7] = 5;
            else if (!sheet[7, 1].HasValue) sheet[7, 1] = 5;
            
            return sheet;
        }
        #endregion
        #region Resize, souřadnice buněk
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            this._CellsCalc();
            this.Refresh();
        }
        private void _CellsCalc()
        {
            if (this._Cells == null) return;

            // Rozpočet:
            Rectangle area = this.ClientRectangle;
            int dim = (area.Width < area.Height ? area.Width : area.Height) - 2;                                          // Vnitřní rozměr panelu, menší ze souřadnic Width, Height
            int border = dim / 100;                             // Vnější okraj, nejméně 2px
            if (border < 2) border = 2;
            int space = 3 * border / 3;                         // Vnitřní oddělovače = 1.5 x border
            int cell = (dim - 2 * border - 2 * space) / 9;      // Rozměr buňky = disponibilní vnitřní prostor dělený 9, oříznutý na Int
            space = (dim - 2 * border - 9 * cell) / 2;          // Upravený prostor oddělovače (upravený tak, aby buňky měly stejnou velikosta aby se využil prostor)
            dim = 2 * border + 2 * space + 9 * cell;            // Upravená velikost (akceptuje zmenšení disponibilního prostoru vlivem dělení volného prostoru na Int32 hodnoty)
            Rectangle bound = new Rectangle(area.X + (area.Width - dim) / 2, area.Y + (area.Height - dim) / 2, dim, dim); // Vlastní rozměr plochy hry, včetně jejích okrajů, zarovnaný
            int x0 = bound.X;
            int y0 = bound.Y;

            // Buňky:
            int y = y0 + border;
            for (int r = 0; r < 9; r++)
            {
                int x = x0 + border;
                for (int c = 0; c < 9; c++)
                {
                    this._Cells[r, c].SetCellArea(x, y, cell);

                    x += cell;
                    if (_AddSpaceAfter(c))
                        x += space;
                }
                y += cell;
                if (_AddSpaceAfter(r))
                    y += space;
            }

            // Fixní plochy:
            int blockSize = 3 * cell;                          // Jeden blok = 3 buňky
            int innerDim = 9 * cell + 2 * space;               // Vnitřní prostor = 9 buněk + 2 vnitřní oddělovače bloků
            
            this.LeftBorderArea = new Rectangle(x0, y0, border, dim);
            this.TopBorderArea = new Rectangle(x0, y0, dim, border);
            this.RightBorderArea = new Rectangle(x0 + dim - border, y0 + border, border, dim - border);
            this.BottomBorderArea = new Rectangle(x0 + border, y0 + dim - border, dim - border, border);

            this.LeftSpaceArea = new Rectangle(x0 + border + blockSize, y0 + border, space, innerDim);
            this.TopSpaceArea = new Rectangle(x0 + border, y0 + border + blockSize, innerDim, space);
            this.RightSpaceArea = new Rectangle(x0 + border + blockSize + space + blockSize, y0 + border, space, innerDim);
            this.BottomSpaceArea = new Rectangle(x0 + border, y0 + border + blockSize + space + blockSize, innerDim, space);
        }
        private static bool _AddSpaceAfter(int cell)
        {
            return ((cell > 0) && (cell % 3) == 2);
        }
        private SudokuCellUI[,] _Cells;
        internal Rectangle LeftBorderArea;
        internal Rectangle TopBorderArea;
        internal Rectangle RightBorderArea;
        internal Rectangle BottomBorderArea;
        internal Rectangle LeftSpaceArea;
        internal Rectangle TopSpaceArea;
        internal Rectangle RightSpaceArea;
        internal Rectangle BottomSpaceArea;
        #endregion
        #region Property
        /// <summary>
        /// Barva prázdné buňky
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva prázdné buňky")]
        public Color EmptyCellColor { get { return this._EmptyCellColor; } set { this._EmptyCellColor = value; this.Refresh(); } }
        private Color _EmptyCellColor;
        /// <summary>
        /// Barva buňky obsahující zadání (Fixní)
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva buňky obsahující zadání (Fixní)")]
        public Color FixedCellColor { get { return this._FixedCellColor; } set { this._FixedCellColor = value; this.Refresh(); } }
        private Color _FixedCellColor;
        /// <summary>
        /// Barva buňky obsahující uživatelem vepsané řešení, které není chybné
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva buňky obsahující uživatelem vepsané řešení, které není chybné")]
        public Color SolvedCellColor { get { return this._SolvedCellColor; } set { this._SolvedCellColor = value; this.Refresh(); } }
        private Color _SolvedCellColor;
        /// <summary>
        /// Barva buňky obsahující uživatelem vepsané řešení, které je chybné
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva buňky obsahující uživatelem vepsané řešení, které je chybné")]
        public Color ErrorCellColor { get { return this._ErrorCellColor; } set { this._ErrorCellColor = value; this.Refresh(); } }
        private Color _ErrorCellColor;
        /// <summary>
        /// Barva okraje buňky, která obsahuje změnu od buňky LastSheet do Sheet
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva okraje buňky, která obsahuje změnu od buňky LastSheet do Sheet")]
        public Color ChangedCellBorderColor { get { return this._ChangedCellBorderColor; } set { this._ChangedCellBorderColor = value; this.Refresh(); } }
        private Color _ChangedCellBorderColor;
        /// <summary>
        /// Barva okraje buňky vpravo a dole (světlejší)
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva okraje buňky vpravo a dole (světlejší)")]
        public Color CellBorderLightColor { get { return this._CellBorderLightColor; } set { this._CellBorderLightColor = value; this.Refresh(); } }
        private Color _CellBorderLightColor;
        /// <summary>
        /// Barva okraje buňky vlevo a nahoře (tmavší)
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva okraje buňky vlevo a nahoře (tmavší)")]
        public Color CellBorderDarkColor { get { return this._CellBorderDarkColor; } set { this._CellBorderDarkColor = value; this.Refresh(); } }
        private Color _CellBorderDarkColor;
        /// <summary>
        /// Barva prostoru mezi bloky buněk (větší mezera)
        /// </summary>
        [Browsable(true)]
        [Category("SudokuUI")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Barva prostoru mezi bloky buněk (větší mezera)")]
        public Color CellBorderInnerColor { get { return this._CellBorderInnerColor; } set { this._CellBorderInnerColor = value; this.Refresh(); } }
        private Color _CellBorderInnerColor;
        /// <summary>
        /// Zobrazovaná data. Po zadání dat do této property se nebudou zobrazovat změnové rámečky okolo buněk, které se mění od předešlého stavu.
        /// Na rozdíl od property SheetChanged.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SudokuSheet Sheet { get { return (this._Sheet == null ? null : this._Sheet.Clone); } set { _LastSheet = null; _Sheet = (value == null ? null : value.Clone); this.Refresh(); } }
        /// <summary>
        /// Zobrazovaná data. Po zadání dat do této property se předešlý stav dat privátně uchová, a při zobrazování aktuálních dat budou okolo změněných buněk vykresleny rámečky.
        /// Na rozdíl od property Sheet.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SudokuSheet SheetChanged { get { return (this._Sheet == null ? null : this._Sheet.Clone); } set { _LastSheet = _Sheet; _Sheet = (value == null ? null : value.Clone); this.Refresh(); } }
        /// <summary>
        /// Minule zobrazovaná data. Slouží ke zvýraznění buněk, kde došlo ke změně.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SudokuSheet LastSheet { get { return (this._LastSheet == null ? null : this._LastSheet.Clone); } set { _LastSheet = (value == null ? null : value.Clone); this.Refresh(); } }
        /// <summary>
        /// Aktuálně zobrazovaná hra
        /// </summary>
        private SudokuSheet _Sheet;
        /// <summary>
        /// Hra zobrazovaná před aktuální hrou.
        /// </summary>
        private SudokuSheet _LastSheet;
        /// <summary>
        /// Smaže řešení, data budou obsahovat jen Fixed hodnoty
        /// </summary>
        public void Clear()
        {
            this._Sheet.Clear();
            this.Refresh();
            this._LastSheet = null;
        }
        #endregion
        #region Cells
        public override void Refresh()
        {
            if (this._Sheet != null)
            {
                this._Sheet.DetectState();
                this.PrepareCells();
            }
            base.Refresh();
        }
        private void PrepareCells()
        {
            if (this._Sheet == null) return;

            for (int r = 0; r < 9; r++)
                for (int c = 0; c < 9; c++)
                    this._Cells[r, c].Cell = this._Sheet.Cells[r, c];
        }
        private class SudokuCellUI
        {
            internal SudokuCellUI(int row, int column)
            {
                this.Row = row;
                this.Column = column;
            }
            internal int Row { get; private set; }
            internal int Column { get; private set; }
            internal bool IsEmptyArea { get { return (this.InnerArea.Width < 5 || this.InnerArea.Height < 5); } }
            internal void SetCellArea(int x, int y, int dim)
            {
                int bw = dim / 25;
                if (bw <= 0) bw = 1;
                int bw1 = bw;
                int bw2 = 2 * bw;
                int bw3 = 3 * bw;
                this.InnerArea = new Rectangle(x + bw, y + bw, dim - bw2, dim - bw2);
                this.ChangeCellPenWidth = (dim < 20 ? 1 : 2);
                this.ChangeCellArea = new Rectangle(x + bw1, y + bw1, dim - this.ChangeCellPenWidth - 1, dim - this.ChangeCellPenWidth - 1);
                this.LeftBorderArea = new Rectangle(x, y, bw, dim);
                this.TopBorderArea = new Rectangle(x, y, dim, bw);
                this.RightBorderArea = new Rectangle(x + dim - bw, y + bw, bw, dim - bw);
                this.BottomBorderArea = new Rectangle(x + bw, y + dim - bw, dim - bw, bw);
            }
            internal Rectangle InnerArea;
            internal Rectangle ChangeCellArea;
            internal int ChangeCellPenWidth;
            internal Rectangle LeftBorderArea;
            internal Rectangle TopBorderArea;
            internal Rectangle RightBorderArea;
            internal Rectangle BottomBorderArea;
            internal SudokuCell Cell { get; set; }
        }
        #endregion
        #region Kreslení
        protected override void  OnPaintToBuffer(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            base.OnPaintToBuffer(sender, e);
            if (this._Cells == null) return;

            this.PaintCells(e);
            
        }
        private void PaintCells(PaintEventArgs e)
        {
            if (this._Sheet == null) return;

            float size = ((float)this._Cells[0, 0].InnerArea.Height) / 2f;
            if (size < 7f) size = 7f;
            float changeWidth = this._Cells[0, 0].ChangeCellPenWidth;
            using (_PaintPalette palette = new _PaintPalette(e, size, changeWidth, this))
            {
                palette.Graphics.FillRectangle(palette.BrushBorderLight, this.LeftBorderArea);
                palette.Graphics.FillRectangle(palette.BrushBorderLight, this.TopBorderArea);
                palette.Graphics.FillRectangle(palette.BrushBorderDark, this.RightBorderArea);
                palette.Graphics.FillRectangle(palette.BrushBorderDark, this.BottomBorderArea);

                palette.Graphics.FillRectangle(palette.BrushInnerSpace, this.LeftSpaceArea);
                palette.Graphics.FillRectangle(palette.BrushInnerSpace, this.TopSpaceArea);
                palette.Graphics.FillRectangle(palette.BrushInnerSpace, this.RightSpaceArea);
                palette.Graphics.FillRectangle(palette.BrushInnerSpace, this.BottomSpaceArea);

                for (int r = 0; r < 9; r++)
                    for (int c = 0; c < 9; c++)
                        this.PaintCell(palette, this._Cells[r, c]);

            }
        }
        /// <summary>
        /// Zobrazení jedné buňky, se vším všudy
        /// </summary>
        /// <param name="e"></param>
        /// <param name="fontR"></param>
        /// <param name="fontB"></param>
        /// <param name="cellUI"></param>
        private void PaintCell(_PaintPalette palette, SudokuCellUI cellUI)
        {
            if (cellUI.IsEmptyArea) return;
            SudokuCell cell = cellUI.Cell;

            Rectangle innerArea = cellUI.InnerArea;

            // Sázka do budoucna (myší interaktivita)
            bool isHot = false;
            bool isSelected = false;

            // Pozadí:
            using (Brush brush = this._GetBackBrush(cellUI, isHot, isSelected))
            {
                palette.Graphics.FillRectangle(brush, innerArea);
            }

            // Text:
            string text = cell.Text;
            if (text.Length > 0)
            {
                Font textFont = (cell.IsFixed ? palette.FontFixed : palette.FontSolved);
                Brush textBrush = (cell.IsFixed ? palette.BrushTextFixed : palette.BrushTextSolved);
                SizeF textSize = palette.Graphics.MeasureString(text, textFont);
                RectangleF textArea = textSize.AlignTo(innerArea, ContentAlignment.MiddleCenter);
                textArea.Y++;
                palette.Graphics.DrawString(text, textFont, textBrush, textArea);
            }

            // Rámeček okolo buňky:
            palette.Graphics.FillRectangle(palette.BrushBorderDark, cellUI.LeftBorderArea);
            palette.Graphics.FillRectangle(palette.BrushBorderDark, cellUI.TopBorderArea);
            palette.Graphics.FillRectangle(palette.BrushBorderLight, cellUI.RightBorderArea);
            palette.Graphics.FillRectangle(palette.BrushBorderLight, cellUI.BottomBorderArea);

            // Rámeček signalizující změnu:
            if (this._LastSheet != null && cell.Text != this._LastSheet.Cells[cellUI.Row, cellUI.Column].Text)
            {
                palette.Graphics.DrawRectangle(palette.ChangePen, cellUI.ChangeCellArea);
            }
        }
        /// <summary>
        /// Vrátí štětec pro kreslení pozadí
        /// </summary>
        /// <param name="cellUI"></param>
        /// <param name="isHot"></param>
        /// <param name="isSelected"></param>
        /// <returns></returns>
        private Brush _GetBackBrush(SudokuCellUI cellUI, bool isHot, bool isSelected)
        {
            Color backColor = _GetBackColor(cellUI.Cell, isHot, isSelected);
            float angle = _GetGradiendAngle(cellUI.Cell, isHot, isSelected);
            LinearGradientBrush lgb = new LinearGradientBrush(cellUI.InnerArea, backColor, backColor.Morph(Color.Gray, 0.25f), angle);
            // lgb.SetBlendTriangularShape(0.25f, 0.75f);
            lgb.SetSigmaBellShape(0.05f, 0.85f);
            return lgb;
        }
        /// <summary>
        /// Vrátí úhel pro linear gradient brush
        /// </summary>
        /// <param name="cell"></param>
        /// <param name="isHot"></param>
        /// <param name="isSelected"></param>
        /// <returns></returns>
        private float _GetGradiendAngle(SudokuCell cell, bool isHot, bool isSelected)
        {
            float angle = 90f;
            if (String.IsNullOrEmpty(cell.Text))
                angle = -90f;
            return angle;
        }
        /// <summary>
        /// Vrátí barvu pro kreslení pozadí
        /// </summary>
        /// <param name="cell"></param>
        /// <param name="isHot"></param>
        /// <param name="isSelected"></param>
        /// <returns></returns>
        private Color _GetBackColor(SudokuCell cell, bool isHot, bool isSelected)
        {
            Color color = this.BackColor;
            if (!cell.HasValue) color = this._EmptyCellColor;
            else if (cell.HasError) color = this._ErrorCellColor;
            else if (cell.IsFixed) color = this._FixedCellColor;
            else color = this._SolvedCellColor;

            if (isHot) return color.Morph(Color.LightGoldenrodYellow, 0.25f);
            if (isSelected) return color.Morph(Color.LightGoldenrodYellow, 0.6f);
            return color;
        }
        private class _PaintPalette : IDisposable
        {
            internal _PaintPalette(PaintEventArgs e, float fontSize, float changeWidth, SudokuUI owner)
            {
                this.PaintArgs = e;
                this.FontFixed = new Font(owner.Font.FontFamily, fontSize, FontStyle.Bold);
                this.FontSolved = new Font(owner.Font.FontFamily, fontSize, FontStyle.Regular);
                this.BrushTextFixed = new SolidBrush(Color.Black);
                this.BrushTextSolved = new SolidBrush(Color.DarkRed);
                this.ChangePen = new Pen(owner.ChangedCellBorderColor, changeWidth);
                this.BrushBorderLight = new SolidBrush(owner.CellBorderLightColor);
                this.BrushBorderDark = new SolidBrush(owner.CellBorderDarkColor);
                this.BrushInnerSpace = new SolidBrush(owner.CellBorderInnerColor);
            }
            internal PaintEventArgs PaintArgs { get; private set; }
            internal Graphics Graphics { get { return this.PaintArgs.Graphics; } }
            internal Font FontFixed { get; private set; }
            internal Font FontSolved { get; private set; }
            internal SolidBrush BrushTextFixed { get; private set; }
            internal SolidBrush BrushTextSolved { get; private set; }
            internal Pen ChangePen { get; private set; }
            internal Brush BrushBorderLight { get; private set; }
            internal Brush BrushBorderDark { get; private set; }
            internal Brush BrushInnerSpace { get; private set; }

            void IDisposable.Dispose()
            {
                if (this.FontFixed != null) this.FontFixed.Dispose();
                if (this.FontSolved != null) this.FontSolved.Dispose();
                if (this.BrushTextFixed != null) this.BrushTextFixed.Dispose();
                if (this.BrushTextSolved != null) this.BrushTextSolved.Dispose();
                if (this.ChangePen != null) this.ChangePen.Dispose();
                if (this.BrushBorderLight != null) this.BrushBorderLight.Dispose();
                if (this.BrushBorderDark != null) this.BrushBorderDark.Dispose();
                if (this.BrushInnerSpace != null) this.BrushInnerSpace.Dispose();

                this.PaintArgs = null;
                this.FontFixed = null;
                this.FontSolved = null;
                this.BrushTextFixed = null;
                this.BrushTextSolved = null;
                this.ChangePen = null;
                this.BrushBorderLight = null;
                this.BrushBorderDark = null;
                this.BrushInnerSpace = null;
            }
        }
        #endregion
    }
}
